﻿using System;
using System.Collections.Generic;
using System.Windows;

using Microsoft.Surface;
using Microsoft.Surface.Presentation;

namespace ENMAX_ArcGIS
{
    public class MapPanZoomController
    {
        private static MapPanZoomController mapPanZoomController = null;

        # region DND and Contacts members

        private const int DragThreshold = 5;

        // Dictionary container to hold the contact ID and the starting point
        // used for panning and zooming events
        private Dictionary<int, Point> contactStartPoints = null;
        private int contactFingerCount = 0;

        private bool isPanning = false;
        private bool isZooming = false;

        double tapZoomfactor = 1.2;

        # endregion               

        #region Singleton Methods

        protected MapPanZoomController()
        {
        }

        public static MapPanZoomController getInstance()
        {
            if (mapPanZoomController == null)
            {
                mapPanZoomController = new MapPanZoomController();
            }

            return mapPanZoomController;
        }

        # endregion

        #region Contact Handlers

        /// <summary>
        /// Update the current state of the contacts on the map
        /// </summary>
        /// <param name="MainMap"></param>
        private void updateContactsInfo(Object map)
        {
            ArcGisMap theMap = map as ArcGisMap;

            // Get all of the contacts over the map area.
            ReadOnlyContactCollection contactsOver = Contacts.GetContactsOver(theMap);

            // reset the container and the counter of contacts
            if (contactStartPoints != null)
            {
                contactStartPoints.Clear();
            }
            contactFingerCount = 0;


            int totalContacts = contactsOver.Count;
            foreach (Contact contact in contactsOver)
            {
                if (contact.IsFingerRecognized)
                {
                    contactFingerCount++;

                    Point centerPos = contact.GetCenterPosition(theMap);
                    //Point pos = contact.GetPosition(MainMap);
                    //Rect boundingRect = contact.BoundingRect;                                     

                    if (contactStartPoints == null)
                    {
                        contactStartPoints = new Dictionary<int, Point>();
                    }
                    contactStartPoints.Add(contact.Id, centerPos);
                }
            }


            // Check all of the contacts and try to detect the gesture
            if (contactFingerCount == 0)
            {
                // do nothing
                isPanning = false;
                isZooming = false;
            }
            else if (contactFingerCount == 1)
            {
                // probably just panning the map
                isZooming = false;
                isPanning = true;
            }
            else if (contactFingerCount == 2)
            {
                // probably zooming in or out
                isZooming = true;
                isPanning = false;
            }
            else
            {
                isZooming = false;
                isPanning = false;
                // probably zooming in or out
                // but maybe also panning
                //isZooming = true;
                //isPanning = true;
            }

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void ContactDownHandler(object sender, ContactEventArgs e)
        {
            ArcGisMap theMap = sender as ArcGisMap;

            // Update the current contact information
            updateContactsInfo(theMap);

            //Acquire Contact capture so theMap will receive all events for this Contact.
            //The LostContactCapture event is used here for notification that this Contact has been
            //completely removed. LostContactCapture is raised after the ContactUp event.
            //When ContactUp is raised, the Contact will still be counted when calling Contacts.GetContactsOver.
            //When LostContactCapture is raised, the Contact will not be counted when calling Contacts.GetContactsOver.
            //Because this application uses Contacts.GetContactsOver to update the status, it is more
            //appropriate to use LostContactCapture instead of ContactUp.
            
            e.Contact.Capture(theMap);
            e.Handled = true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void LostContactCaptureHandler(object sender, ContactEventArgs e)
        {
            updateContactsInfo(sender);
            e.Handled = true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void ContactTapGestureHandler(object sender, ContactEventArgs e)
        {
            ArcGisMap theMap = sender as ArcGisMap;
                                    
            e.Contact.Capture(theMap);

            System.Windows.Point p = e.GetPosition(theMap);

            theMap.PanTo(p);
            theMap.Zoom(tapZoomfactor);

            e.Handled = true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void PreviewContactChangedHandler(object sender, ContactEventArgs e)
        {
            ArcGisMap theMap = sender as ArcGisMap;

            if (theMap != null)
            {
                try
                {
                    if (isPanning)
                    {
                        TryStartPanning(theMap, e);
                    }

                    if (isZooming)
                    {
                        TryStartZooming(theMap, e);
                    }
                }
                catch
                {
                    throw;
                }

                if (e.Handled)
                {
                    updateContactsInfo(theMap);
                }
            }

            e.Handled = true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="theMap"></param>
        /// <param name="e"></param>
        private void TryStartPanning(ArcGisMap theMap, ContactEventArgs args)
        {
            if (contactFingerCount != 1 ||
                contactStartPoints.Count != 1)
            {
                return;
            }

            try
            {
                Point currentPosition = args.GetPosition(theMap);
                Point downPosition = contactStartPoints[args.Contact.Id];
                Vector draggedDelta = currentPosition - downPosition;

                // if this contact has moved less than Threshold pixels vertically or horizontally
                // then this is not a panning event yet
                if (Math.Abs(draggedDelta.Y) < DragThreshold ||
                    Math.Abs(draggedDelta.X) < DragThreshold)
                {
                    return;
                }

                // The idea is to find out a new center for the map by applying the displacement and angle of the contact
                // change to the original center of the map
                Point centerPosition = theMap.Center;
                Point newCenter = centerPosition - 2 * draggedDelta;

                // Pan to the new center point            
                theMap.PanTo(newCenter);

                args.Handled = true;
            }
            catch (Exception e)
            {
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="theMap"></param>
        /// <param name="e"></param>
        private void TryStartZooming(ArcGisMap theMap, ContactEventArgs args)
        {
            if (contactFingerCount != 2 ||
                contactStartPoints.Count != 2)
            {
                return;
            }

            try
            {
                // Get the current distance between the two contacts            
                bool foundFirst = false;
                int numberOfContactsFound = 0;
                Point firstNewPoint = new Point();
                Point secondNewPoint = new Point();
                Point firstOriginalPoint = new Point();
                Point secondOriginalPoint = new Point();

                ReadOnlyContactCollection contactsOver = Contacts.GetContactsOver(theMap);
                foreach (Contact contact in contactsOver)
                {
                    if (contact.IsFingerRecognized)
                    {
                        Point centerPos = contact.GetCenterPosition(theMap);
                        if (!foundFirst)
                        {
                            firstNewPoint = centerPos;
                            firstOriginalPoint = contactStartPoints[contact.Id];

                            if (firstNewPoint == null || firstOriginalPoint == null)
                            {
                                continue;
                            }

                            foundFirst = true;
                            numberOfContactsFound++;
                        }
                        else
                        {
                            secondNewPoint = centerPos;
                            secondOriginalPoint = contactStartPoints[contact.Id];
                            if (secondNewPoint == null || secondOriginalPoint == null)
                            {
                                continue;
                            }

                            numberOfContactsFound++;
                            break;
                        }
                    }
                }

                if (numberOfContactsFound != 2)
                {
                    return;
                }

                Vector originalVector = firstOriginalPoint - secondOriginalPoint;
                double originalDistance = Math.Sqrt(Math.Abs(originalVector.Y) * Math.Abs(originalVector.Y) + Math.Abs(originalVector.X) * Math.Abs(originalVector.X));

                Vector newVector = firstNewPoint - secondNewPoint;
                double newDistance = Math.Sqrt(Math.Abs(newVector.Y) * Math.Abs(newVector.Y) + Math.Abs(newVector.X) * Math.Abs(newVector.X));

                // Figure out the zooming factor
                double zoomFactor = 1;
                if (originalDistance < newDistance)
                {
                    // Zooming in
                    zoomFactor = 1 + 0.5 * newDistance / originalDistance;
                }
                else if (originalDistance > newDistance)
                {
                    // Zooming out
                    zoomFactor = 1 - 0.3 * newDistance / originalDistance;
                }

                // Zoom the map
                theMap.Zoom(zoomFactor);
                
                args.Handled = true;
            }
            catch (Exception e)
            {
            }
        }

        # endregion


    }
}
